package com.pay.ioopos.support.scan;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.os.Handler;

import androidx.camera.core.CameraX;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageAnalysisConfig;
import androidx.camera.core.ImageProxy;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.RGBLuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeReader;
import com.pay.ioopos.ScanListener;

import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static androidx.camera.core.ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE;

public class ScanFace implements ScanCase {

    private boolean isScaned = false;
    private ScanListener listener;
    private BarcodeFormat format;
    private ExecutorService service = Executors.newFixedThreadPool(10);
    private LifecycleOwner lifecycleOwner;
    private Handler handler = new Handler();

    public ScanFace(BarcodeFormat format) {
        this.format = format;
    }

    @Override
    public void bindToLifecycle(LifecycleOwner lifecycleOwner) {
        this.lifecycleOwner = lifecycleOwner;
        ImageAnalysisConfig config = new ImageAnalysisConfig.Builder()
                .setImageQueueDepth(10)
                .setImageReaderMode(ACQUIRE_LATEST_IMAGE)
                //.setTargetResolution(new Size(1080, 720))
                .build();
        ImageAnalysis imageAnalysis = new ImageAnalysis(config);
        imageAnalysis.setAnalyzer((image, rotationDegrees) -> imageAnalysis(image));
        CameraX.bindToLifecycle(lifecycleOwner, imageAnalysis);
    }

    @Override
    public void setScanListener(ScanListener listener) {
        this.listener = listener;
    }

    private void imageAnalysis(final ImageProxy image) {
        if (isScaned || lifecycleOwner.getLifecycle().getCurrentState() == Lifecycle.State.DESTROYED) {
            return;
        }
        byte[] bytes = getBytes(image);
        int width = image.getWidth();
        int height = image.getHeight();
        service.execute(() -> analysis(bytes, width, height));

    }

    private byte[] getBytes(ImageProxy imageProxy) {
        android.media.Image image = imageProxy.getImage();
        android.media.Image.Plane[] planes = image.getPlanes();
        ByteBuffer yBuffer = planes[0].getBuffer(); // Y
        ByteBuffer uBuffer = planes[1].getBuffer(); // U
        ByteBuffer vBuffer = planes[2].getBuffer(); // V

        int ySize = yBuffer.remaining();
        int uSize = uBuffer.remaining();
        int vSize = vBuffer.remaining();

        byte[] nv21 = new byte[ySize + uSize + vSize];

        //U and V are swapped
        yBuffer.get(nv21, 0, ySize);
        vBuffer.get(nv21, ySize, vSize);
        uBuffer.get(nv21, ySize + vSize, uSize);
        return nv21;
    }

    private void analysis(byte[] nv21, int width, int height) {
        Bitmap bitmap = convert(nv21, width, height);
        String result = decodeByZXing(bitmap);

        if (!isScaned && null != result && !result.isEmpty()) {
            isScaned = true;
            handler.post(() -> isScaned = listener.onScan(result));
        }
    }

    private String decodeByZXing(Bitmap bitmap) {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        final int[] pixels = new int[width * height];
        bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
        RGBLuminanceSource luminanceSource = new RGBLuminanceSource(width, height, pixels);
        BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(luminanceSource));

        try {
            final Map<DecodeHintType, Object> hints = new HashMap<>();
            hints.put(DecodeHintType.CHARACTER_SET, "utf-8");
            //hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
            Result result;
            if (null != format && format == BarcodeFormat.QR_CODE) {
                hints.put(DecodeHintType.POSSIBLE_FORMATS, format);
                result = new QRCodeReader().decode(binaryBitmap, hints);
            } else {
                hints.put(DecodeHintType.ALLOWED_EAN_EXTENSIONS, false);
                hints.put(DecodeHintType.ASSUME_GS1, false);
                hints.put(DecodeHintType.ASSUME_CODE_39_CHECK_DIGIT, false);
                hints.put(DecodeHintType.PURE_BARCODE, Boolean.TRUE);
                //hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
                result = new MultiFormatReader().decode(binaryBitmap, hints);
            }
            return result.toString();
        } catch (Exception e) {
            return null;
        }
    }

    private Bitmap convert(byte[] nv21, int width, int height) {
        YuvImage yuvImage = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        yuvImage.compressToJpeg(new Rect(0, 0, yuvImage.getWidth(), yuvImage.getHeight()), 50, out);
        byte[] imageBytes = out.toByteArray();
        return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
    }

}
